﻿using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Caching.Memory;
using System.Net;
using System.Reflection;
using System.Security.Claims;

namespace 解决JWT无法撤回的问题
{
    public class JWTValidationFilter : IAsyncActionFilter
    {
        private IMemoryCache memCache;

        public JWTValidationFilter(IMemoryCache memCache)
        {
            this.memCache = memCache;

        }

        public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
        {

            ControllerActionDescriptor? ctrlActionDesc=context.ActionDescriptor as ControllerActionDescriptor;

            //如果不是Controller的Action,让其继续执行
            if (ctrlActionDesc==null)
            {
                await next();
                return;
            }

            //Action上标记了NotCheckJWTVersionAttribute,让其继续执行
            if (ctrlActionDesc.MethodInfo.GetCustomAttributes(typeof(NotCheckJWTVersionAttribute),true).Any())
            {
                await next();
                return;
            }
           

            var username = context.HttpContext.User.FindFirst(ClaimTypes.Name);
            //对于登录接口等没有登录,直接跳过
            if (username == null)
            {
                await next();
                return;
            }

            string cacheKey = $"JWTValidationFilter.UserInfo.{username}";

            UserModel user = await memCache.GetOrCreateAsync(cacheKey, async e =>
            {
                e.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(5);
                return DataCenter.UserData[username.Value];
            });

            //获取Clamin(payload)中用户名,判断用户是否存在(可能被删除或者禁用了)
            if (user == null)
            {
                var result = new ObjectResult($"{username} not found!");
                result.StatusCode = (int)HttpStatusCode.Unauthorized;
                context.Result = result;
                return;
            }

            // 判断Clamin(payload)中的JWTVersion是否存在
            var claimVersion = context.HttpContext.User.FindFirst(ClaimTypes.Version);

            if (claimVersion == null)
            {
                context.Result = new ObjectResult("payload中没有JWTVersion") { StatusCode = 400 };
                return;
            }

            //jwt中保存的版本号
            long jwtVerOfReq = long.Parse(claimVersion!.Value);

            //由于内存缓存等导致的并发问题，
            //假如集群的A服务器中缓存保存的还是版本为5的数据，但客户端提交过来的可能已经是版本号为6的数据。
            //因此只要是客户端提交的版本号>=服务器上取出来（可能是从Db，也可能是从缓存）的版本号，那么也是可以的
            if (jwtVerOfReq >= user.JWTVersion)
            {
                await next();
            }
            else
            {
                var result = new ObjectResult("客户端的jwt过时");
                result.StatusCode = (int)HttpStatusCode.Unauthorized;
                context.Result = result;
                return;
            }

        }
    }
}
